/* Surface.cpp
Copyright (c) 2008 Michael Zahniser
Please note the license terms (MIT license) at the end of this document.

Function definitions for the Surface class.
*/

#include "Surface.h"

#include "PixMap.h"
#include "_common/min_max.h"

#include <algorithm>
#include <cmath>

using std::max;



// Helper functions (defined below).
namespace {
	void DrawHLine(PixMap &pix, int y, int left, int right, Uint32 color);
	void DrawVLine(PixMap &pix, int x, int top, int bottom, Uint32 color);
}



// Default constructor.
Surface::Surface()
	: data(NULL)
{
}



// Constructor:
// Create a surface wrapping a raw SDL_Surface.
// If the SDL_Surface is owned by another Surface in addition to this one,
// you must manually increment its reference count after using this constructor,
// or else construct this Surface straight from that other Surface.
Surface::Surface(SDL_Surface *surface)
	: data(surface)
{
	if(data)
		bounds.Set(0, 0, data->w, data->h);
	// There is no need to retain the surface here, because it already has a reference
	// count of 1 from being created.
}



// Copy constructor:
Surface::Surface(const Surface &surface)
	: data(surface.data), bounds(surface.bounds)
{
	Retain();
}



// Destructor:
Surface::~Surface()
{
	Release();
}



// operator=:
Surface &Surface::operator=(const Surface &surface)
{
	// Check for self-assignment, which could cause trouble here.
	if(data != surface.data)
	{
		Release();
		data = surface.data;
		Retain();
	}
	bounds = surface.bounds;

	return *this;
}



// bool typecast:
// Check to see whether this Surface is associated with an image.
Surface::operator bool() const
{
	return !!*this;
}



// operator!:
// Returns true if no image is associated with this Surface.
bool Surface::operator!() const
{
	return !data;
}



// Get a "window" into this surface.
Surface Surface::Region(int x, int y, int width, int height) const
{
	return Region(Rectangle(x, y, width, height));
}



// Get a "window" into this surface.
Surface Surface::Region(Rectangle rect) const
{
	rect.Move(bounds.x, bounds.y);

	// If the given rectangle is not contained in this surface, return a
	// null surface.
	rect &= bounds;
	if(!rect)
		return Surface();

	Surface surface(*this);
	surface.bounds = rect;

	return surface;
}



// Make a deep copy of this surface, which can be edited separately from this one.
Surface Surface::MakeCopy() const
{
	return SDL_ConvertSurface(data, data->format, 0);
}



// Get the underlying SDL_Surface.
SDL_Surface *Surface::Data()
{
	return data;
}


const SDL_Surface *Surface::Data() const {
	return data;
}


// Width:
// Get the width of the surface.
int Surface::Width() const
{
	return bounds.w;
}



// Height:
// Get the height of the surface.
int Surface::Height() const
{
	return bounds.h;
}



// Color:
// Map the given RGB values to a pixel value.
Uint32 Surface::Color(int red, int green, int blue) const
{
	return SDL_MapRGB(data->format, red, green, blue);
}



// Color:
// Map a greyscale level to a color.
Uint32 Surface::Color(int greyLevel) const
{
	return Color(greyLevel, greyLevel, greyLevel);
}



// HLine:
// Draw a horizontal 1-pixel-tall line of the given color.
void Surface::HLine(int x, int y, int width, Uint32 color)
{
	PixMap pix(*this);
	DrawHLine(pix, y, x, x + width, color);
}



// VLine:
// Draw a vertical 1-pixel-wide line of the given color.
void Surface::VLine(int x, int y, int height, Uint32 color)
{
	PixMap pix(*this);
	DrawVLine(pix, x, y, y + height, color);
}



// Frame:
// Draw a 1-pixel-wide frame inside the given rectangle coordinates.
void Surface::Frame(int x, int y, int width, int height, Uint32 color)
{
	Frame(Rectangle(x, y, width, height), color);
}



// Frame:
// Draw a 1-pixel-wide frame inside the given rectangle coordinates.
void Surface::Frame(Rectangle rect, Uint32 color)
{
	PixMap pix(*this);

	DrawHLine(pix, rect.Top(), rect.Left(), rect.Right(), color);
	DrawHLine(pix, rect.Bottom() - 1, rect.Left(), rect.Right(), color);
	DrawVLine(pix, rect.Left(), rect.Top() + 1, rect.Bottom() - 1, color);
	DrawVLine(pix, rect.Right() - 1, rect.Top() + 1, rect.Bottom() - 1, color);
}



// FillRect:
// Fill the given rectangle.
void Surface::FillRect(int x, int y, int width, int height, Uint32 color)
{
	FillRect(Rectangle(x, y, width, height), color);
}



// FillRect:
// Fill the given rectangle.
void Surface::FillRect(Rectangle rect, Uint32 color)
{
	// Make sure we don't draw outside the bounds.
	minimize(rect.w, bounds.w - rect.x);
	minimize(rect.h, bounds.h - rect.y);

	// Shift the rectangle to its true position in the larger surface.
	rect.Move(bounds.x, bounds.y);
	SDL_FillRect(data, &rect, color);
}



// FillRoundRect:
// Fill in a rectangle with "rounded" corners.
// (Actually, just 45 degree angle).
void Surface::FillRoundRect(int x, int y, int width, int height, int radius, Uint32 color)
{
	FillRoundRect(Rectangle(x, y, width, height), radius, color);
}



void Surface::FillRoundRect(Rectangle rect, int radius, Uint32 color)
{
	// Shift the rectangle to its true position in the larger surface.
	rect.Move(bounds.x, bounds.y);

	PixMap pix(*this);
	for(int y = rect.Top(); y < rect.Bottom(); ++y)
	{
		int offset = std::max(0, std::max(
			rect.Top() + radius - y, 
			y + radius - (rect.Bottom() - 1)));
		int startX = rect.Left() + offset;
		int endX = std::min(rect.Right() - offset, bounds.Right());
		
		DrawHLine(pix, y, startX, endX, color);
	}
}



// OverlayRect:
// Overlay the given color on the given rectangular region.
void Surface::OverlayRect(int x, int y, int width, int height, Uint32 color)
{
	OverlayRect(Rectangle(x, y, width, height), color);
}



// OverlayRect:
// Overlay the given color on the given rectangular region.
void Surface::OverlayRect(Rectangle rect, Uint32 color)
{
	// Make sure we don't draw outside the bounds.
	minimize(rect.w, bounds.w - rect.x);
	minimize(rect.h, bounds.h - rect.y);

	// Average the given color with all pixels in the rectangle.
	color >>= 1;
	color &= 0x7F7F7F7F;
	// Round brighter colors up. This makes sure that if you overlay
	// 0xFF on 0xFF, you get 0xFF.
	color += (color >> 6) & 0x01010101;
	
	PixMap pix(*this);
	for(int y = rect.Top(); y < rect.Bottom(); ++y)
	{
		Uint32 *it = pix.Begin(y) + rect.Left();
		Uint32 *end = it + rect.Width();
		
		for( ; it != end; ++it)
			*it = ((*it >> 1) & 0x7F7F7F7F) + color;
	}
}



// Erase:
// Fill the entire surface with the given color.
void Surface::Erase(Uint32 color)
{
	SDL_FillRect(data, &bounds, color);
}



// Blit:
// Draw this surface at the given location of the given surface.
void Surface::Blit(Surface &destination, int x, int y) const
{
	Blit(0, 0, Width(), Height(), destination, x, y);
}



// Blit:
// Draw the given region of this surface at the given location of the given surface.
void Surface::Blit(int sourceX, int sourceY, int width, int height, Surface &destination, int x, int y) const
{
	Blit(Rectangle(sourceX, sourceY, width, height), destination, x, y);
}



// Blit:
// Draw the given region of this surface at the given location of the given surface.
void Surface::Blit(Rectangle sourceRect, Surface &destination, int x, int y) const
{
	Blit(sourceRect, destination, Rectangle(x, y, sourceRect.w, sourceRect.h));
}



// Blit:
// Draw the given region of this surface at the given location of the given surface.
void Surface::Blit(Rectangle sourceRect, Surface &destination, Rectangle destinationRect) const
{
	int leftInset = max(max(-sourceRect.x, -destinationRect.x), 0);
	int topInset = max(max(-sourceRect.y, -destinationRect.y), 0);
	int rightInset = max(sourceRect.w + max(sourceRect.x - bounds.w, destinationRect.x - destination.bounds.w), 0);
	int bottomInset = max(sourceRect.h + max(sourceRect.y - bounds.h, destinationRect.y - destination.bounds.h), 0);

	sourceRect.w -= leftInset + rightInset;
	sourceRect.h -= topInset + bottomInset;

	if(sourceRect.w <= 0 || sourceRect.h <= 0)
		return;

	sourceRect.Move(leftInset + bounds.x, topInset + bounds.y);
	destinationRect.Move(leftInset + destination.bounds.x, topInset + destination.bounds.y);

	// Trust that SDL_BlitSurface will not modify the input rectangles.
	SDL_BlitSurface(data, &sourceRect, destination.data, &destinationRect);
}



// Retain:
// Increment this Surface's reference count.
void Surface::Retain()
{
	if(data)
		++data->refcount;
}



// Release:
// Decrement this Surface's reference count and free its memory if this is the last reference to it.
void Surface::Release()
{
	SDL_FreeSurface(data);
}



// Draw a horizontal line in the given image, using the given color.
namespace {
	void DrawHLine(PixMap &pix, int y, int left, int right, Uint32 color)
	{
		// Check the input parameters, and crop the line to the surface if necessary.
		if(y < 0 || y >= pix.Height() || left >= right || left >= pix.Width() || right < 0)
			return;
		maximize(left, 0);
		minimize(right, pix.Width());

		Uint32 *it = pix.Begin(y) + left;
		Uint32 *end = it + right - left;

		for( ; it != end; ++it)
			*it = color;
	}
}



// Draw a vertical line in the given image, using the given color.
namespace {
	void DrawVLine(PixMap &pix, int x, int top, int bottom, Uint32 color)
	{
		// Check the input parameters, and crop the line to the surface if necessary.
		if(x < 0 || x >= pix.Width() || top >= bottom || top >= pix.Height() || bottom < 0)
			return;
		maximize(top, 0);
		minimize(bottom, pix.Height());

		Uint32 *it = pix.Begin(top) + x;
		Uint32 *end = it + pix.Pitch() * (bottom - top);

		for( ; it != end; it += pix.Pitch())
			*it = color;
	}
}




/* Copyright (c) 2008 Michael Zahniser

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE. */
